#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <../arch/arm/mach-s3c2410/include/mach/regs-gpio.h>
#include <../arch/arm/mach-s3c2410/include/mach/hardware.h>




#define CONF(p,d)	s3c2410_gpio_cfgpin((p),(d))
#define SET(p,d)	s3c2410_gpio_setpin((p),(d))
#define GET(p)		s3c2410_gpio_getpin((p))

#define SCLK		S3C2410_GPG14
#define DATA		S3C2410_GPG13

#define DATAIN		S3C2410_GPG13_INP
#define DATAOUT		S3C2410_GPG13_OUTP

#define SCLKIN		S3C2410_GPG14_INP
#define SCLKOUT		S3C2410_GPG14_OUTP


#define noACK 0
#define ACK   1
                            //adr  command  r/w
#define STATUS_REG_W 0x06   //000   0011    0
#define STATUS_REG_R 0x07   //000   0011    1
#define MEASURE_TEMP 0x03   //000   0001    1
#define MEASURE_HUMI 0x05   //000   0010    1
#define RESET        0x1e   //000   1111    0

//----------------------------------------------------------------------------------
// modul-var
//----------------------------------------------------------------------------------
#define DEV_NAME	"HW_SHT11"

static struct sht_device{
	dev_t sht_dev;
	struct cdev *sht_cdev;
	struct class *sht_class;
	struct semaphore sem;
};

static struct sht_device *dev;


enum {TEMP,HUMI};

static int sht_open (struct inode *i, struct file *f);
static ssize_t sht_read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
static int sht_close(struct inode *inode, struct file *file);


static char s_write_byte(unsigned char value);
static char s_read_byte(unsigned char ack);
static void s_transstart(void);
static void s_connectionreset(void);
static char s_softreset(void);
static char s_read_statusreg(unsigned char *p_value, unsigned char *p_checksum);
static char s_write_statusreg(unsigned char *p_value);
static char s_measure(unsigned int *p_value, unsigned char *p_checksum, unsigned char mode) ;

//----------------------------------------------------------------------------------
static char s_write_byte(unsigned char value) 
//----------------------------------------------------------------------------------
// writes a byte on the Sensibus and checks the acknowledge 
{ 
	unsigned char i,error=0; 
	msleep(1);
	CONF(DATA,DATAOUT);
	msleep(1);
	for (i=0x80;i>0;i/=2) //shift bit for masking 
	{
		if (i & value) SET(DATA,1); //masking value with i , write to SENSI-BUS 
		else SET(DATA,0); 
		msleep(1);
		SET(SCLK,1); //clk for SENSI-BUS 
		msleep(1);
		SET(SCLK,0); 
		msleep(1);
	} 
	SET(DATA,1); //release DATA-line 
	msleep(1);
	SET(SCLK,1); //clk #9 for ack 
	msleep(1);
	CONF(DATA,DATAIN);
	msleep(1);
	error=GET(DATA); //check ack (DATA will be pulled down by SHT11) 
	SET(SCLK,0); 
	msleep(1);
	return error; //error=1 in case of no acknowledge 
} 
//----------------------------------------------------------------------------------
static char s_read_byte(unsigned char ack) 
//----------------------------------------------------------------------------------
// reads a byte form the Sensibus and gives an acknowledge in case of "ack=1" 
{ 
	unsigned char i,val=0; 
	msleep(1);
	CONF(DATA,DATAOUT);
	msleep(1);
	SET(DATA,1); //release DATA-line 
	msleep(1);
	CONF(DATA,DATAIN);
	for (i=0x80;i>0;i/=2) //shift bit for masking 
	{ 
		SET(SCLK,1); //clk for SENSI-BUS 
		msleep(1);
		if (GET(DATA)) val=(val | i); //read bit 
		SET(SCLK,0); 
		msleep(1);
	} 
	CONF(DATA,DATAOUT);
	SET(DATA,!ack); //in case of "ack==1" pull down DATA-Line 
	msleep(1);
	SET(SCLK,1); //clk #9 for ack 
	msleep(1);
	SET(SCLK,0); 
	msleep(1);
	SET(DATA,1); //release DATA-line 
	msleep(1);
	return val; 
} 
//----------------------------------------------------------------------------------
static void s_transstart(void) 
//----------------------------------------------------------------------------------
// generates a transmission start 
// _____ ________ 
// DATA: |_______| 
// ___ ___ 
// SCK : ___| |___| |______ 
{ 
	msleep(1);
	CONF(DATA,DATAOUT);
	msleep(1);
	SET(DATA,1); 
	msleep(1);
	SET(SCLK,0); //Initial state 
	msleep(1);
	SET(SCLK,1); 
	msleep(1); 
	SET(DATA,0); 
	msleep(1); 
	SET(SCLK,0); 
	msleep(1);
	SET(SCLK,1); 
	msleep(1); 
	SET(DATA,1); 
	msleep(1); 
	SET(SCLK,0); 
	msleep(1);
} 

//----------------------------------------------------------------------------------
static void s_connectionreset(void) 
//----------------------------------------------------------------------------------
// communication reset: DATA-line=1 and at least 9 SCK cycles followed by transstart 
// _____________________________________________________ ________ 
// DATA: |_______| 
// _ _ _ _ _ _ _ _ _ ___ ___ 
// SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______| |___| |______ 
{ 
	unsigned char i; 
	msleep(1);
	CONF(DATA,DATAOUT);
	msleep(1);
	SET(DATA,1);
	msleep(1);
	SET(SCLK,0); //Initial state 
	msleep(1);
	for(i=0;i<9;i++) //9 SCK cycles 
	{
		SET(SCLK,1); 
		msleep(1);
		SET(SCLK,0);
		msleep(1); 
	} 
	s_transstart(); //transmission start 
} 
//----------------------------------------------------------------------------------
static char s_softreset(void) 
//----------------------------------------------------------------------------------
// resets the sensor by a softreset 
{ 
	unsigned char error=0; 
	s_connectionreset(); //reset communication 
	error+=s_write_byte(RESET); //send RESET-command to sensor 
	return error; //error=1 in case of no response form the sensor 
} 
//----------------------------------------------------------------------------------
static char s_read_statusreg(unsigned char *p_value, unsigned char *p_checksum) 
//----------------------------------------------------------------------------------
// reads the status register with checksum (8-bit) 
{ 
	unsigned char error=0; 
	s_transstart(); //transmission start 
	error=s_write_byte(STATUS_REG_R); //send command to sensor 
	*p_value=s_read_byte(ACK); //read status register (8-bit) 
	*p_checksum=s_read_byte(noACK); //read checksum (8-bit) 
	return error; //error=1 in case of no response form the sensor 
} 
//----------------------------------------------------------------------------------
static char s_write_statusreg(unsigned char *p_value) 
//----------------------------------------------------------------------------------
// writes the status register with checksum (8-bit) 
{ 
	unsigned char error=0; 
	s_transstart(); //transmission start 
	error+=s_write_byte(STATUS_REG_W);//send command to sensor 
	error+=s_write_byte(*p_value); //send value of status register 
	return error; //error>=1 in case of no response form the sensor 
} 
static char s_measure(unsigned int *p_value, unsigned char *p_checksum, unsigned char mode) 
//----------------------------------------------------------------------------------
// makes a measurement (humidity/temperature) with checksum 
{ 
	unsigned error=0; 
	unsigned int i=0;
	unsigned char t[2];
	s_transstart(); //transmission start 
	switch(mode)
	{ //send command to sensor 
		case TEMP : error+=s_write_byte(MEASURE_TEMP); break; 
		case HUMI : error+=s_write_byte(MEASURE_HUMI); break; 
		default : break; 
	} 
	CONF(DATA,DATAIN);
	msleep(1);
	/*for (i=0;i<10000000;i++)
	if(GET(DATA)==0) break; *///wait until sensor has finished the measurement 
	//msleep(1);
	while(GET(DATA))
	{
		udelay(1);
		i++;
		if(i==1000)
			break;
	}
	msleep(1);
	if(GET(DATA)) error+=1; // or timeout (~2 sec.) is reached 
	t[0] =s_read_byte(ACK); //read the first byte (MSB) 
	t[1]=s_read_byte(ACK); //read the second byte (LSB) 
	*p_value=t[0];
	*p_value=(*p_value<<8);
	*p_value+=t[1];
	*p_checksum =s_read_byte(noACK); //read checksum 
	return error; 
}

static ssize_t sht_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)
{
	char temper[2];
	char humi[2];
	unsigned char error,checksum; 
	s_connectionreset(); 

	error=0; 
	error+=s_measure(temper,&checksum,HUMI); //measure humidity 
	error+=s_measure(humi,&checksum,TEMP); //measure temperature 
	
		
	printk("humi=%d,temper=%d,error=%d\n",humi,temper,error);
		
	if(error!=0) 
		s_connectionreset(); 
	else
	{    
		copy_to_user(buff,temper,2);   
		copy_to_user(buff+2,humi,2);   
		return 0;
	} 
	return -1;
}

static int sht_open (struct inode *i, struct file *f)
{
	unsigned char error,checksum; 
	unsigned int humi_val_i,temp_val_i; 
	if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	CONF(SCLK,SCLKOUT);
	s3c2410_gpio_pullup(DATA,0);
	
	s_connectionreset(); 
	error=0; 
	error+=s_measure(&humi_val_i,&checksum,HUMI); //measure humidity 
	error+=s_measure(&temp_val_i,&checksum,TEMP);
	//mdelay(800);
	if(error>0)
	{
		printk("uable to open sht!\n");
		return -1;
	}
	else
	{
		printk("sht opened!\n");
		return 0;
	}
}

static int sht_close(struct inode *inode, struct file *file)
{
	printk(KERN_NOTICE"I've closed sht11!!\n");
	up(&dev->sem);
	return 0;
}

struct file_operations sht_fops = {
	.owner =  THIS_MODULE, 
	.open =	  sht_open, 
	.read =	sht_read,
}; 


static int __init sht_init(void)
{
	int sht_major,sht_minor,r;
	dev=kmalloc(sizeof(struct sht_device),GFP_KERNEL);
	init_MUTEX(&dev->sem);
	if(alloc_chrdev_region(&dev->sht_dev, 0, 1, DEV_NAME)<0){
		printk(KERN_WARNING "Can't get major !!\n");
		return 1;
	}
	else{
		sht_major=MAJOR(dev->sht_dev);
		sht_minor=MINOR(dev->sht_dev);
		printk("The major is %d, and the minor is %d!\n",sht_major,sht_minor);
	}
//\u52a8\u6001\u5206\u914d\u8bbe\u5907\u7f16\u53f7

//register char device	
	dev->sht_cdev= cdev_alloc();
	if(dev->sht_cdev!=NULL){
		cdev_init(dev->sht_cdev, &sht_fops);
		dev->sht_cdev->ops = &sht_fops;
		dev->sht_cdev->owner=THIS_MODULE;	

		r=cdev_add(dev->sht_cdev, dev->sht_dev, 1);
		if(r){
			printk(KERN_NOTICE "Error %d adding sht11", r);
			return 1;
		}
		else{
			dev->sht_class = class_create(THIS_MODULE, DEV_NAME);
			if(IS_ERR(dev->sht_class)){
				 printk("Err: failed in sht11 class. \n");
				 return 1;
			}
			device_create(dev->sht_class, NULL, dev->sht_dev, NULL, DEV_NAME);
			printk(KERN_NOTICE "Adding sht11 is successfully!!\n");
			return 0;
		}	
	}
	else
		return 1;
//register char device	
}

static void __exit sht_exit(void)
{
	cdev_del(dev->sht_cdev);
	unregister_chrdev_region(dev->sht_dev, 1);	
	device_destroy(dev->sht_class, dev->sht_dev);
	class_destroy(dev->sht_class);
	kfree(dev);
	printk(KERN_ALERT "Goodbye, cruel world\n");
}

MODULE_DESCRIPTION("Sht11 driver for hw");
MODULE_LICENSE("GPL");
module_init(sht_init);
module_exit(sht_exit);
